﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace OpenLS.Spreadsheet.Formats.Biff.Records
{
    internal class BkHimRecord : BiffRecord, IWorksheetLevelRecord
    {

        static byte[] GetBytes (BitmapSource source)
        {
            Contract.Requires(source != null);
            int width = (int) source.Width;
            int height = (int) source.Height;
            int bytesPerPixel = 3;
            int test = source.Format.BitsPerPixel;
            if (test == 32)
                bytesPerPixel = 4;
            byte [] bytes = new byte[width * height * bytesPerPixel];
            source.CopyPixels(bytes, width * bytesPerPixel,0 );
            bytes = CompressBytes(bytes, bytesPerPixel);
            bytesPerPixel = 3;
            Contract.Assert(bytes.Length == width * height * bytesPerPixel);
            var stride = width*bytesPerPixel * 8 /8;
            InvertLines(bytes, height, stride);
            Contract.Assert(bytes.Length == width * height * bytesPerPixel);
            byte [] result = new byte[bytes.Length + 12];
            using (MemoryStream ms = new MemoryStream(result))
            {
                using(BinaryWriter writer = new BinaryWriter(ms))
                {
                    writer.Write((uint)12);
                    writer.Write((ushort)source.Width);
                    writer.Write((ushort)source.Height);
                    writer.Write((ushort)1);
                    writer.Write((ushort)24);
                    writer.Write(bytes);
                }
            }
            Contract.Assert(bytes.Length + 12 == result.Length);
            return result;
        }

        private static byte[] CompressBytes(byte[] bytes, int bytesPerPixel)
        {
            if (bytesPerPixel == 3)
                return bytes;
            byte [] result = new byte[bytes.Length * 3 / 4];
            using (MemoryStream ms = new MemoryStream(result))
            {
                using (BinaryWriter writer = new BinaryWriter(ms))
                {
                    for (int i = 0; i < bytes.Length / 4; i++)
                    {
                        writer.Write(bytes[i * 4]);
                        writer.Write(bytes[i * 4+1]);
                        writer.Write(bytes[i * 4+2]);
                    }
                }
            }
            return result;
        }

        internal static  IEnumerable<BiffRecord> GetRecords(BitmapSource source)
        {
            return ContinueRecordHelper.GetRecords((i, b) => new BkHimRecord(i, b), GetBytes(source));
           /* var bytes = GetBytes(source);
            List<Byte[]> brokenBytes = new List<byte[]>();
            for (int i = 0; i < bytes.Length; i += 8000)
            {
                int length = bytes.Length - i;
                if (length > 8000)
                    length = 8000;
                byte[] item = new byte[length];
                Array.Copy(bytes, i, item, 0, item.Length);
                brokenBytes.Add(item);
            }
            yield return new BkHimRecord(bytes.Length, brokenBytes[0]);
            foreach (var item in Enumerable.Skip(brokenBytes, 1))
                yield return new ContinueRecord() {Buffer = item};*/
        }

        private ushort cf = 9;
        private ushort reserved = 1;
        private byte[] _imageBlob;
        private byte[] imageBlob
        {
            get { return _imageBlob; }
            set
            {
                Contract.Requires(value != null);
                Contract.Requires(value.Length < 9000);
                _imageBlob = value;
            }
        }

        internal override BiffRecordType RecordType
        {
            get { return BiffRecordType.BkHim; }
        }

        public void Apply(BiffReaderWorksheetContext context)
        {
            if (cf != 9) return;
            var remainingBytes = Enumerable.ToList(Enumerable.Skip(allBytes, 12));
            byte[] buffer = this.allBytes.ToArray();
            using (BinaryReader reader = new BinaryReader(new MemoryStream(buffer)))
            {

                var temp = reader.ReadUInt32();
                Contract.Assert(temp == 12);
                ushort width = reader.ReadUInt16();
                ushort height = reader.ReadUInt16();
                ushort planes = reader.ReadUInt16();
                Contract.Assert(planes == 1);
                ushort bitsPerPixel = reader.ReadUInt16();
                if (bitsPerPixel != 24)
                    return;
                var stride = width*bitsPerPixel/8;
                byte[] pixels = remainingBytes.ToArray();
                Contract.Assert(pixels.Length == width * height * bitsPerPixel / 8);
                InvertLines(pixels, height, stride);
                
                context.Worksheet.Background = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr24, null, pixels, stride);
            }
        }

        private static void InvertLines(byte[] pixels, int height, int stride)
        {
            byte [] line = new byte[stride];
            for (int i = 0; i < height / 2; i++ )
            {
                Array.Copy(pixels, stride * i, line, 0, stride);
                Array.Copy(pixels, stride * (height - 1 - i), pixels, i * stride, stride);
                Array.Copy(line, 0, pixels, stride * (height - 1 - i), stride);
            }
        }

        internal override void WriteContent(BiffWriterWorkbookContext context)
        {
            context.Write(cf);
            context.Write(reserved);
            context.Write(lcb);
            context.Write(imageBlob);
        }

        protected override void ReadContent(BiffReaderContext context, int length)
        {
#if DEBUG
            var oldPosition = context.Reader.BaseStream.Position;
#endif
            this.cf = context.ReadUInt16();
            this.reserved = context.ReadUInt16();
            this.lcb = context.ReadUInt32();
            this.imageBlob = context.ReadBytes(length - 4 -2  -2);
#if DEBUG
            var newPosition = context.Reader.BaseStream.Position;
            Contract.Assert(length == newPosition - oldPosition);
#endif
        }

        private List<byte> allBytes;
        private uint lcb;

        internal BkHimRecord(int length, byte[] bytes)
        {

            Contract.Requires(length >= 0);
            Contract.Requires(bytes != null);
            Contract.Requires(bytes.Length < 9000);
            Contract.Requires(bytes.Length <= length);
            this.lcb = (uint)length;
            this.imageBlob = bytes;
        }

        public BkHimRecord()
        {
        }

        public override void ReadWithContinue()
        {
            this.allBytes = new List<byte>(imageBlob);
            var continueRecord = Next as ContinueRecord;
            while (continueRecord != null)
            {
                allBytes.AddRange(continueRecord.Buffer);
                continueRecord = continueRecord.Next as ContinueRecord;
            }
            Contract.Assert(allBytes.Count == lcb);

        }
    }
}